package com.ninth.element.generated.number.generate.redis;


import com.ninth.element.generated.number.NumberStarterConstants;
import com.ninth.element.generated.number.generate.common.constant.SimpleDateFormatter;
import com.ninth.element.generated.number.generate.fallback.DefaultIdFallback;
import com.ninth.element.generated.number.generate.fallback.IdFallback;
import com.ninth.element.generated.number.generate.common.pojo.enums.ResetTypeEnum;
import com.ninth.element.generated.number.property.RedisWithNumberProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @author LiYUJi
 */
public class RedisIdTemplate implements InitializingBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisIdTemplate.class);

    @Autowired
    private Environment environment;

    @Autowired
    private RedisWithNumberProperty redisWithNumberProperty;

    @Autowired
    private RedisIdWorker redisIdWorker;

    @Autowired
    private DefaultIdFallback defaultIdFallback;

    private RedisIdPatternConfiguration redisIdPatternConfiguration;

    private String appName = "NUMBER_DEFAULT_APP";

    public RedisIdTemplate() {
        this.redisIdPatternConfiguration = RedisIdPatternConfiguration.builder().build();
    }

    public RedisIdTemplate(RedisIdPatternConfiguration redisIdPatternConfiguration) {
        this.redisIdPatternConfiguration = redisIdPatternConfiguration;
    }

    /**
     * 按规则生成ID
     *
     * @param key               redis key名称，同时作为ID前缀（若配置了需要前缀）
     * @param dateTimeFormatter 日期格式，为空则不拼接日期，默认null
     * @param digits            redis id位数，id不足位数则在前面补0，默认0
     * @param increment         redis id增长步长，默认1
     * @param initial           redis id初始化值，默认从0开始
     * @param needPrefix        是否需要前缀，默认false不拼接
     * @param isResetDaily      是否需要重置序列，默认false不重置，为true的话序列会每天重置为initial的参数值，一般搭配dateTimeFormatter参数一起使用
     * @param resetType         重置类型 ResetTypeEnum
     * @param idFallback        redis不可用时，服务降级的ID生成策略
     * @return ID
     */
    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits, Integer increment, Long initial, boolean needPrefix, boolean isResetDaily, ResetTypeEnum resetType, IdFallback idFallback) {
        Assert.hasLength(key, "key must not be null or empty!");
        StringBuffer seq = new StringBuffer();
        if (needPrefix && !StringUtils.isEmpty(key)) {
            seq.append(key);
        }
        try {
            // 如果开启了重置，而没有设置重置类型则 默认为每天
            if (isResetDaily && resetType == null) {
                resetType = ResetTypeEnum.DAY;
                LOGGER.warn("当前参数配置了每天重置序列(isResetDaily=true)，但未配置日期重置类型(resetType)，系统将为您配置默认的resetType参数为 DAY，建议您配置isResetDaily=true的同时配置resetType参数");
            }
            String dateStr = (dateTimeFormatter != null) ? dateTimeFormatter.format(LocalDateTime.now()) : "";
            // 设置为永不过期--目前先这样可以后期优化
            long ttl = -1;
            Long id = redisIdWorker.incr(getRedisKeyName(key, isResetDaily, resetType), increment, initial, ttl);
            String formatRedisId = digits > 0 ? String.format("%0" + digits + "d", id) : String.valueOf(id);
            return seq.append(dateStr).append(formatRedisId).toString();
        } catch (Exception ex) {
            //为了容错和服务降级, 当Redis不能提供服务或其他异常错误导致生成失败时
            LOGGER.error("通过Redis生成唯一ID失败，采用服务降级策略生成", ex);
        }
        //没有配置ID服务降级策略，则使用默认策略
        idFallback = (idFallback == null) ? defaultIdFallback : idFallback;
        return seq.append(idFallback.nextId()).toString();
    }

    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits, Long initial, boolean needPrefix, boolean isResetDaily, ResetTypeEnum resetType) {
        return next(key, dateTimeFormatter, digits, redisIdPatternConfiguration.getIncrement(), initial, needPrefix, isResetDaily, resetType, redisIdPatternConfiguration.getIdFallback());
    }

    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits, Long initial, boolean needPrefix, boolean isResetDaily) {
        return next(key, dateTimeFormatter, digits, redisIdPatternConfiguration.getIncrement(), initial, needPrefix, isResetDaily, ResetTypeEnum.DAY, redisIdPatternConfiguration.getIdFallback());
    }

    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits, Long initial, boolean needPrefix) {
        return next(key, dateTimeFormatter, digits, initial, needPrefix, redisIdPatternConfiguration.isResetDaily);
    }

    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits, Long initial) {
        return next(key, dateTimeFormatter, digits, initial, redisIdPatternConfiguration.isNeedPrefix());
    }

    public String next(String key, DateTimeFormatter dateTimeFormatter, int digits) {
        return next(key, dateTimeFormatter, digits, redisIdPatternConfiguration.getInitial());
    }

    public String next(String key, int digits) {
        return next(key, redisIdPatternConfiguration.getDateTimeFormatter(), digits);
    }

    public String next(String key, Long initial) {
        return next(key, redisIdPatternConfiguration.getDateTimeFormatter(), redisIdPatternConfiguration.getDigits(), initial);
    }

    public String next(String key) {
        return next(key, redisIdPatternConfiguration.getDigits());
    }

    public String next(Long initial) {
        return next(redisIdPatternConfiguration.getKey(), initial);
    }

    public String next() {
        return next(redisIdPatternConfiguration.getKey());
    }

    /**
     * 拼接完成的redis前缀 appName_key_UID【_格式化的时间】
     * 如果没有开启时间重置，则不拼接
     */
    public String getRedisKeyName(String key, boolean isResetDaily, ResetTypeEnum resetType) {
        StringBuffer redisKey = new StringBuffer().append(appName.toUpperCase());
        if (!StringUtils.isEmpty(key)) {
            redisKey.append("_").append(key.toUpperCase());
        }
        redisKey.append("_UID");

        // 根据类型选择时间格式化
        if (isResetDaily) {
            DateTimeFormatter dateTimeFormatterLocal = SimpleDateFormatter.FORMATTER_DAY;
            switch (resetType) {
                case YEAR:
                    dateTimeFormatterLocal = SimpleDateFormatter.FORMATTER_YEAR;
                    break;
                case MONTH:
                    dateTimeFormatterLocal = SimpleDateFormatter.FORMATTER_MONTH;
                    break;
                case SECOND:
                    dateTimeFormatterLocal = SimpleDateFormatter.FORMATTER_SECOND;
                    break;
                case MILLISECOND:
                    dateTimeFormatterLocal = SimpleDateFormatter.FORMATTER_MILLISECOND;
                    break;
                case DAY:
                default:
                    break;
            }
            redisKey.append("_").append(dateTimeFormatterLocal.format(LocalDateTime.now()));
        }
        return redisKey.toString();
    }

    public String getAppName() {
        String applicationName = redisWithNumberProperty.getAppName();
        if (StringUtils.isEmpty(applicationName)) {
            if (environment != null && !StringUtils.isEmpty(environment.getProperty(NumberStarterConstants.SPRING_APPLICATION_NAME))) {
                applicationName = environment.getProperty(NumberStarterConstants.SPRING_APPLICATION_NAME);
            } else {
                applicationName = this.appName;
            }
        }
        return applicationName;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.appName = getAppName();
    }
}
